namespace SimpleWebServices.Web.UI.Html.Elements
{
    using System;
    using System.Xml;
    using System.Xml.Serialization;
    using SimpleWebServices.Configuration;
    using SimpleWebServices.Data;
    using SimpleWebServices.Models;

    [XmlRoot("form")]
    public class Form : InnerElements, IXmlSerializable
    {
        private string _method;

        public Form()
        {
            this.Method = "post";
        }

        public bool Completed
        {
            get;
            set;
        }

        public Uri Action
        {
            get;
            set;
        }

        public int ErrorCount
        {
            get
            {
                return Form.CountErrors(this.Token, this);
            }
        }

        public bool IsValid
        {
            get
            {
                return 0 == this.ErrorCount;
            }
        }

        public string Method
        {
            get
            {
                return this._method;
            }

            set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("value");
                }
                else if ("get".Equals(value, StringComparison.OrdinalIgnoreCase))
                {
                    this._method = "get";
                }
                else if ("post".Equals(value, StringComparison.OrdinalIgnoreCase))
                {
                    this._method = "post";
                }
                else
                {
                    throw new ArgumentOutOfRangeException("value");
                }
            }
        }

        private AbsoluteUri Urn
        {
            get;
            set;
        }

        private Token? Token
        {
            get;
            set;
        }

        public override IElement Deserialize(string name, string type, string xml)
        {
            IElement result = null;

            if (null == type && "form".Equals(name, StringComparison.OrdinalIgnoreCase))
            {
                result = xml.Deserialize<Form>();
            }

            return result;
        }

        public void Retrieve(Token? token)
        {
            if (token.HasValue && !Guid.Empty.Equals(token.Value))
            {
                this.Update(Record.Retrieve<FormSession>(token.Value));
            }
        }

        public Token? Store(DataCollection form)
        {
            FormSession session = new FormSession
            {
                Data = form,
                Token = this.Token,
                Urn = this.Urn
            };

            if (!this.Token.HasValue || Guid.Empty.Equals(this.Token.Value))
            {
                session.Urn = FormSession.FormatUrn(Guid.NewGuid());
                session.Store();
                this.Token = session.Token;
                this.Urn = session.Urn;
            }

            session.Store();

            return session.Token;
        }

        public void Update(FormSession session)
        {
            this.Update(null == session ? null as DataCollection : session.Data);
        }

        public void Update(DataCollection data)
        {
            if (null != data)
            {
                if (null != data.Token)
                {
                    this.Token = data.Token;
                }

                if (null != data.Urn)
                {
                    this.Urn = data.Urn;
                }

                this.Completed = data.Completed;
                foreach (var datum in data)
                {
                    IElement element = this[datum.Key];
                    Fieldset fieldset = element as Fieldset;
                    if (null == fieldset)
                    {
                        (this[datum.Key] as IControl).Update(datum.Key, data[datum.Key]);
                    }
                    else
                    {
                        CheckBoxInput checkbox = this[string.Concat(datum.Key, ":", datum.Value)] as CheckBoxInput;
                        if (null != checkbox)
                        {
                            checkbox.Checked = true;
                        }
                        else
                        {
                            RadioInput radio = this[string.Concat(datum.Key, ":", datum.Value)] as RadioInput;
                            if (null != radio)
                            {
                                radio.Checked = true;
                            }
                        }
                    }
                }
            }
        }

        public override void ReadXml(XmlReader reader)
        {
            string value = reader.GetAttribute("action");
            if (!string.IsNullOrEmpty(value))
            {
                this.Action = new Uri(value, UriKind.RelativeOrAbsolute);
            }

            value = reader.GetAttribute("method");
            if (!string.IsNullOrEmpty(value))
            {
                this.Method = value;
            }

            base.ReadXml(reader);
        }

        public override void WriteXml(XmlWriter writer)
        {
            if (null != this.Action)
            {
                writer.WriteAttributeString("action", this.Action.IsAbsoluteUri ? this.Action.AbsoluteUri : this.Action.OriginalString);
            }

            if (!string.IsNullOrEmpty(this.Method))
            {
                writer.WriteAttributeString("method", this.Method);
            }

            base.WriteXml(writer);
        }

        private static int CountErrors(Token? token, IInnerElements obj)
        {
            int result = 0;

            if (null != obj)
            {
                foreach (IElement element in obj.Elements)
                {
                    result += Form.CountErrors(token, element as IInnerElements);

                    IValidation control = element as IValidation;
                    if (null != control)
                    {
                        result += control.IsValid ? 0 : 1;
                    }
                }
            }

            return result;
        }
    }
}